6.4.1 原子函数

原子函数能够以很底层的加锁机制来同步访问整型变量和指针。我们可以用原子函数来修正代码清单6-9中创建的竞争状态,如代码清单6-13所示。

代码清单6-13 listing13.go

01 // 这个示例程序展示如何使用atomic包来提供
02 // 对数值类型的安全访问
03 package main
04
05 import (
06   "fmt"
07   "runtime"
08   "sync"
09   "sync/atomic"
10 )
11
12 var (
13   // counter是所有goroutine都要增加其值的变量
14   counter int64
15
16   // wg用来等待程序结束
17   wg sync.WaitGroup
18 )
19
20 // main是所有Go程序的入口
21 func main() {
22   // 计数加2,表示要等待两个goroutine
23   wg.Add(2)
24
25   // 创建两个goroutine
26   go incCounter(1)
27   go incCounter(2)
28
29   // 等待goroutine结束
30   wg.Wait()
31
32   // 显示最终的值
33   fmt.Println("Final Counter:", counter)
34 }
35
36 // incCounter增加包里counter变量的值
37 func incCounter(id int) {
38   // 在函数退出时调用Done来通知main函数工作已经完成
39   defer wg.Done()
40
41   for count := 0; count < 2; count++ {
42     // 安全地对counter加1
43     atomic.AddInt64(&counter, 1)
44
45     // 当前goroutine从线程退出,并放回到队列
46     runtime.Gosched()
47   }
48 }

对应的输出如代码清单6-14所示。

代码清单6-14 listing13.go的输出

Final Counter: 4

现在,程序的第43行使用了 atmoic 包的 AddInt64 函数。这个函数会同步整型值的加法,方法是强制同一时刻只能有一个goroutine运行并完成这个加法操作。当goroutine试图去调用任何原子函数时,这些goroutine都会自动根据所引用的变量做同步处理。现在我们得到了正确的值4。

另外两个有用的原子函数是 LoadInt64StoreInt64 。这两个函数提供了一种安全地读和写一个整型值的方式。代码清单6-15中的示例程序使用 LoadInt64StoreInt64 来创建一个同步标志,这个标志可以向程序里多个goroutine通知某个特殊状态。

代码清单6-15 listing15.go

01 // 这个示例程序展示如何使用atomic包里的
02 // Store和Load类函数来提供对数值类型
03 // 的安全访问
04 package main
05
06 import (
07   "fmt"
08   "sync"
09   "sync/atomic"
10   "time"
11 )
12
13 var (
14   // shutdown是通知正在执行的goroutine停止工作的标志
15   shutdown int64
16
17   // wg用来等待程序结束
18   wg sync.WaitGroup
19 )
20
21 // main是所有Go程序的入口
22 func main() {
23   // 计数加2,表示要等待两个goroutine
24   wg.Add(2)
25
26   // 创建两个goroutine
27   go doWork("A")
28   go doWork("B")
29
30   // 给定goroutine执行的时间
31   time.Sleep(1 * time.Second)
32
33   // 该停止工作了,安全地设置shutdown标志
34   fmt.Println("Shutdown Now")
35   atomic.StoreInt64(&shutdown, 1)
36
37   // 等待goroutine结束
38   wg.Wait()
39 }
40
41 // doWork用来模拟执行工作的goroutine, 
42 // 检测之前的shutdown标志来决定是否提前终止
43 func doWork(name string) {
44   // 在函数退出时调用Done来通知main函数工作已经完成
45   defer wg.Done()
46
47   for {
48     fmt.Printf("Doing %s Work\n", name)
49     time.Sleep(250 * time.Millisecond)
50
51     // 要停止工作了吗?
52     if atomic.LoadInt64(&shutdown) == 1 {
53       fmt.Printf("Shutting %s Down\n", name)
54       break
55     }
56   }
57 }

在这个例子中,启动了两个goroutine,并完成一些工作。在各自循环的每次迭代之后,在第52行中goroutine会使用 LoadInt64 来检查 shutdown 变量的值。这个函数会安全地返回 shutdown 变量的一个副本。如果这个副本的值为1,goroutine就会跳出循环并终止。

在第35行中, main 函数使用 StoreInt64 函数来安全地修改 shutdown 变量的值。如果哪个 doWork goroutine试图在 main 函数调用 StoreInt64 的同时调用 LoadInt64 函数,那么原子函数会将这些调用互相同步,保证这些操作都是安全的,不会进入竞争状态。

results matching ""

    No results matching ""